package image.util;

import com.sun.imageio.plugins.jpeg.*;

import javax.imageio.IIOImage;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.plugins.jpeg.JPEGImageWriteParam;
import javax.imageio.stream.FileImageInputStream;
import javax.imageio.stream.FileImageOutputStream;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.VolatileImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.*;

/**
 * Created by IntelliJ IDEA.
 * User: localadmin
 * Date: 13.01.2011
 * Time: 10:01:41
 */
public class JPEGResizer {
    private static GraphicsDevice monitor;
    private File source;
    private File target;
    private int maxSize = -1;
    private int width = -1;
    private int height = -1;
    private float quality = 0.85f;

    private IIOMetadata metadata;
    private BufferedImage image;

    public JPEGResizer(File source, File target) {
        if (source == null || !source.exists()) {
            throw new IllegalArgumentException("source file must exist");
        }
        if (target == null) {
            throw new IllegalArgumentException("null target file");
        }
        this.source = source;
        this.target = target;
    }

    public void setMaxSize(int maxSize) {
        if (maxSize <= 20) {
            throw new IllegalArgumentException("illegal max size");
        }
        this.maxSize = maxSize;
    }

    public void setWidth(int width) {
        if (width <= 0) {
            throw new IllegalArgumentException("illegal image width");
        }
        this.width = width;
    }

    public void setHeight(int height) {
        if (height <= 0) {
            throw new IllegalArgumentException("illegal image height");
        }
        this.height = height;
    }

    public void setQuality(float quality) {
        if (quality < 0 || quality > 1) {
            throw new IllegalArgumentException("illegal quality");
        }
        this.quality = quality;
    }

    public void resize() {
        try {
            read();
            resizeImage();
            write();
        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }

    private void read() throws IOException {
        FileImageInputStream imageStream = new FileImageInputStream(source);
        JPEGImageReader reader = new JPEGImageReader(new JPEGImageReaderSpi());
        reader.setInput(imageStream);
        metadata = reader.getImageMetadata(0);
        image = reader.read(0);
        imageStream.close();
    }

    private void write() throws IOException {
        JPEGImageWriteParam param = new JPEGImageWriteParam(Locale.getDefault());
        param.setCompressionMode(JPEGImageWriteParam.MODE_EXPLICIT);
        param.setCompressionQuality(quality);
        param.setOptimizeHuffmanTables(true);
        JPEGImageWriter writer = new JPEGImageWriter(new JPEGImageWriterSpi());

        FileImageOutputStream imageOutStream = new FileImageOutputStream(target);
        writer.setOutput(imageOutStream);
        writer.write(null, new IIOImage(image, null, metadata), param);
        imageOutStream.close();
        try {
            FileOutputStream os = new FileOutputStream("c:/temp/metadata.bin");
            try {
                Class segmentClass = Class.forName("com.sun.imageio.plugins.jpeg.MarkerSegment");
                Class mdataClass = JPEGMetadata.class;
                Field markers = mdataClass.getDeclaredField("markerSequence");
                Field dataField = segmentClass.getDeclaredField("data");
                dataField.setAccessible(true);

                markers.setAccessible(true);
                java.util.List mlist = (java.util.List) markers.get(metadata);
                Object segment = mlist.get(0);
                byte[] data = (byte[]) dataField.get(segment);
                os.write(data);
            } finally {
                os.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void resizeImage() {
        int nwidth;
        int nheight;

        if (maxSize != -1) {
            double scale = (double) image.getWidth() / (double) image.getHeight();
            if (scale >= 1) {
                nwidth = maxSize;
                nheight = (int) (maxSize / scale);
            } else {
                nwidth = (int) (maxSize * scale);
                nheight = maxSize;
            }
        } else if (width > 0 && height > 0) {
            nwidth = width;
            nheight = height;
        } else {
            throw new IllegalStateException("size parameters didn't set correctly");
        }

        int width = image.getWidth();
        int height = image.getHeight();


        if (width == nwidth && height == nheight) {
            return;
        }

        if (nwidth <= 0) {
            nwidth = (int) ((double) width * ((double) nheight / (double) height));
        } else if (nheight <= 0) {
            nheight = (int) ((double) height * ((double) nwidth / (double) width));
        }

        double xscale = 1;
        double yscale = 1;

        if (nwidth > 2 * width) {
            xscale = 0.5;
        } else if (width > 2 * nwidth) {
            xscale = 2;
        }
        if (nheight > 2 * height) {
            yscale = 0.5;
        } else if (height > 2 * nheight) {
            yscale = 2;
        }

        GraphicsDevice monitor = getMonitor();
        BufferedImage buf = image;
        //fast resize if source image 2x bigger/smaller than target
        if (yscale != 1 || xscale != 1) {
            VolatileImage vbuf = monitor.getDefaultConfiguration().createCompatibleVolatileImage((int) (xscale * nwidth), (int) (yscale * nheight));
            Graphics2D gbuf = vbuf.createGraphics();
            gbuf.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
            gbuf.drawImage(image, 0, 0, (int) (xscale * (double) nwidth), (int) (yscale * (double) nheight), null);
            buf = vbuf.getSnapshot();
        }
        VolatileImage out = monitor.getDefaultConfiguration().createCompatibleVolatileImage(nwidth, nheight);
        Graphics2D g = out.createGraphics();
        g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
        g.drawImage(buf, 0, 0, nwidth, nheight, null);
        image = out.getSnapshot();
    }

    private static GraphicsDevice getMonitor() {
        if (monitor == null) {
            GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
            GraphicsDevice gd[] = ge.getScreenDevices();
            for (int i = 0; i < gd.length && monitor == null; ++i) {
                if (gd[i].getType() == 0) {
                    monitor = gd[i];
                }
            }
        }
        return monitor;
    }
}
